<?php

class Rapide_ACL implements Rapide_ACL_Interface {
	const ACL_CHECK		= 0;
	const ACL_ALLOW_ALL = 1;
	const ACL_DENY_ALL	= 2;

	private $_bAnonymous = true;
	public $aRole = array();

	private $_iState = self::ACL_CHECK;
	private $_aRules;
	private $_iSize = 0;

	public function __construct() {
		/**
		 * Create root node
		 */
		$this->_insertNode(-1, '', true);
	}

	public function setRole(array $aRole, $bAnonymous = false) {

		$this->_bAnonymous = $bAnonymous;
		$this->aRole['name'] = $aRole['name'];
		$this->aRole['root'] = $aRole['root'];
	}

	public function addRules(Rapide_ACL_Loader_Interface $oAclLoader) {
		if($this->_iState == self::ACL_CHECK) {
			$oAclLoader->loadRules($this);
		}
	}

	public function setState($iState) {
		$this->_iState = $iState;
	}

	public function isAnonymous() {
		return $this->_bAnonymous;
	}

	public function isAllowed(array $aResource) {

		switch($this->_iState) {
			case self::ACL_CHECK:
				$iId = 0;
				$iCountResource = count($aResource);
				foreach($aResource as $i => $sItem) {
					if(is_null($iId = $this->_findNode($iId, $sItem))) {
						return isset($iParentId) ? $this->_aRules[$iParentId][1] : false;
					}
					elseif($i == $iCountResource - 1) {
						return $this->_aRules[$iId][1];
					}

					$iParentId = $iId;
				}
				return false;
			case self::ACL_ALLOW_ALL:
				return true;
			case self::ACL_DENY_ALL:
				return false;
		}
	}

	public function insertRule($aResource, $bAccess = true) {

		if(!is_array($aResource)) {
			throw new Rapide_ACL_Exception('Resource for new rule in Rapide_ACL must be an array');
		}

		$iId = 0;
		$iIndex = NULL;
		$iCountResource = count($aResource);
		foreach($aResource as $i => $sItem) {
			if(is_null($iIndex = $this->_findNode($iId, $sItem))) {
				$iIndex = $this->_insertNode($iId, $sItem, $i != $iCountResource - 1 ? false : $bAccess);
			}
			elseif($i == $iCountResource - 1) {
				$this->_aRules[$iIndex][1] = $bAccess;
			}

			$iId = $iIndex;
		}
	}

	private function _insertNode($iParentId, $sName, $bAccess) {
		$this->_aRules[$this->_iSize] = array(
			0 => $sName,		// Name
			1 => $bAccess,	// Access
			2 => $iParentId, // Parent
			3 => -1,			// Next node
			4 => -1,			// First child
			5 => -1			 // Last child
		);

		if($iParentId >= 0) {
			if($this->_aRules[$iParentId][5] != -1) {
				$this->_aRules[$this->_aRules[$iParentId][5]][3] = $this->_iSize;
				$this->_aRules[$iParentId][5] = $this->_iSize;
			}
			else {
				$this->_aRules[$iParentId][5] = $this->_aRules[$iParentId][4] = $this->_iSize;
			}
		}

		$this->_iSize++;
		return $this->_iSize - 1;
	}

	private function _findNode($iParentId, $name) {

		if(isset($this->_aRules[$iParentId])) {
			$iId = $this->_aRules[$iParentId][4];
			while($iId != -1) {
				if($this->_aRules[$iId][0] == $name) {
					return $iId;
				}
				$iId = $this->_aRules[$iId][3];
			}				
		}
		
		return NULL;	
	}

}

?>